import java.util.*;
import java.io.*;

import FileSystem.FileWorker;

/**
Короткая справка.

JShell - кроссплатформенная командная оболочка.

Команды:

cd [ПУТЬ]		Меняет директорию на ПУТЬ, либо возвращает в домашнюю директорию, если ПУТЬ не задан;
copy <ЧТО> <КУДА>	Копирует файл или папку (рекурсивно) с именем ЧТО в папку КУДА;
delete <ИМЯ>		Удаляет файл или папку (рекурсивно) с именем ИМЯ;
dir [ПУТЬ] [МАСКА]	Показывает содержимое папки ПУТЬ (удовлетворяющее маске МАСКА, если она задана);
help			Справка;
mkfile <ИМЯ>		Создаёт новый файл с именем ИМЯ (если файл с таким именем отсутствует);
mkdir <ИМЯ>		Создаёт новую папку с именем ИМЯ (если папка с таким именем отсутствует);
rename <СТАРОЕ> <НОВОЕ> Переименовывает (перемещает) файл или папку (рекурсивно) с имени СТАРОЕ на имя НОВОЕ;
sort <ПУТЬ> <ТИП>	Сортирует содержимое папки ПУТЬ по типу ТИП:
	+name			Сортировка по имени (по возрастанию);
	-name			Соритровка по имени (по убыванию);
	+size			Сортировка по размеру (по возрастанию);
	-size			Сортировка по размеру (по убыванию);
	+lmod			Сортировка по дате последнего изменения (по возрастанию); 
	-lmod			Сортировка по дате последнего изменения (по убыванию);
*/

/* Особенности и нерешённые проблемы:
1. Пробелы в именах файлов экранируются "\" в *nix-системах либо "^" в Windows;
2. При указании маски в команде "dir" обязательно указание пути ПУТЬ (текущая папка указывается как ".");
3. Аналогично в "sort";
4. 
*/

public class JShell {
	public static void main (String[] args) {
	
		Scanner in = new Scanner(System.in); // create buffered input stream
		String[] command;
		JShellCommands cmd = new JShellCommands(System.getProperty("user.home")); // Home user directory
		
		boolean exit = false;
		
		cmd.help();
		do {
			System.out.print(cmd.getHendDir() + "> "); // input invite
			
			command = cmd.split(in.nextLine());
			
		} while (cmd.handlerCommand(command));
	} 
}


class JShellCommands {

	private FileWorker worker;
	private File home; // Home directory // Домашняя папка
	private char escapeCharacter;

	JShellCommands (String startDir) /*overload*/ {
		worker = new FileWorker(startDir);
		home = new File(System.getProperty("user.home"));
		
		escapeCharacter = (isWindows()) ? '^' : '\\'; // escape-character
	}
	
	JShellCommands (File startDir) /*overload*/ {
		worker = new FileWorker(startDir);
		home = new File(System.getProperty("user.home"));
		
		escapeCharacter = (isWindows()) ? '^' : '\\';
	}
	
	public String[] split (String cmdLine) {
	/* Парсинг параметров */
		try {
			ArrayList <String> myArgs = new ArrayList();
		
			String[] command = cmdLine.split(" ");
		
			for (int i = 0; i < command.length; i++) {
				if (command[i] == null) // null-строки отбрасываем
					continue;
				
				/* // Debug information // Отладочная информация
				System.out.println(command[i] + " LIof = "+command[i].lastIndexOf(escapeCharacter)+ " len ="+command[i].length());
				*/
				
				int lastIndex = command[i].length() - 1;
			
				if (command[i].lastIndexOf(escapeCharacter) == lastIndex && i != command.length - 1) { // Если последний символ - "\", и он не является последним символом в имени файла, то это экран
					command[i] = command[i].substring(0, lastIndex) + " " + command[i+1]; // Присоединяем следующий кусок параметра
					
					for (int j = i + 1; j < command.length - 1; j++) { // сдвигаем хвост массива на шаг к началу
						command[j] = command[j+1];
					}
					command[command.length - 1] = null; // в освободившийся конец массива пишем null
					
					if (command[i].lastIndexOf(escapeCharacter) != command[i].length() - 1) {
						myArgs.add(command[i]); // Если в конце нет экрана - параметр готов
					} else
						i--; // Иначе преобразуем его снова
					
				} else {
					myArgs.add(command[i]); // Остальное просто пихаем без преобразования
				}
				
				/* // Debug information // Отладочная информация
				System.out.println("i="+i+" c[i]="+command[i]); */
			}
			return myArgs.toArray(new String[0]);
		} catch (ArrayIndexOutOfBoundsException e) {
			System.err.println("Illegal parameters of command.");
			return null;
		}
	}
	
	public boolean handlerCommand (String[] cmd) {
		/**
		
		*/
		try {
			if (cmd == null || cmd.length == 0)
				return false;
		
			if (cmd[0].equals("exit")) {
				return false;
			} else if (cmd[0].equals("help")) {
				help();
			} else if (cmd[0].equals("cd")) {
				switch (cmd.length) {
				case 1:
					worker.cd(home); // Go to home directory
					break;
				case 2:
					worker.cd(cmd[1]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else if (cmd[0].equals("dir")) {
				switch (cmd.length) {
				case 1:
					dir(".");
					break;
				case 2:
					dir(cmd[1]);
					break;
				case 3:
					dir(cmd[1], cmd[2]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else if (cmd[0].equals("copy")) {
				switch (cmd.length) {
				case 3:
					copy(cmd[1], cmd[2]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else if (cmd[0].equals("delete")) {
				switch (cmd.length) {
				case 2:
					delete(cmd[1]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else if (cmd[0].equals("rename")) {
				switch (cmd.length) {
				case 3:
					rename(cmd[1],cmd[2]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else if (cmd[0].equals("mkdir")) {
				switch (cmd.length) {
				case 2:
					mkDir(cmd[1]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else if (cmd[0].equals("mkfile")) {
				switch (cmd.length) {
				case 2:
					mkFile(cmd[1]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else if (cmd[0].equals("sort")) {
				switch (cmd.length) {
				case 3:
					sort(cmd[1],cmd[2]);
					break;
				default:
					throw new CommandException("Illegal parameters of command.");
				}
			} else {
				System.out.println("Unknown command: " + cmd[0]);
			}
		} catch (CommandException e) {
			System.err.println(e.getMessage());
//			for (short i = 0; i < cmd.length; i++) {
//				System.err.println(cmd[i]);
//			}
		}
		
		return true;
	}
	
	public String getHendDir() {
		return worker.hendDir.toString();
	}
	
	/* Create a new file */
	
	private void mkFile (String newFile) {
		if (!worker.createFile(".",newFile))
			System.err.println("Cannot create a file.");
	}
	
	/* Create new directory */
	
	private void mkDir (String newDir) {
		if (!worker.createDirectory(".",newDir))
			System.err.println("Cannot create a directory.");
	}
	
	/* Copy the file */
	
	private void copy (String src, String dest) {
		copy (new File(src), new File(dest));
	}
	
	private void copy (File src, File dest) {
		if (!worker.copy(src, dest))
			System.err.println("Cannot copy a file. " + worker.textError);
	}
	
	/* Rename the file */
	
	private void rename (String oldName, String newName) /*overload*/ {
		if (!worker.rename(oldName, newName))
			System.err.println("Impossible renamed: " + oldName);
	}
	
	/* Delete a file or directory */
	private void delete (String name) {
		if (!worker.delete(name))
			System.err.println("Impossible to delete: " + name);
	}
	
	/* Contents of a directory */
	private void dir(String f) /*overload*/ {
		dir(new File(f), null);
	}
	
	private void dir(String f, String mask) /*overload*/ {
		dir(new File(f), mask);
	}
	
	private void dir(File f) /*overload*/ {
		dir(f, null);
	}
	
	private void dir(File f, String mask) /*overload*/ {
		try {
			Collection <File> files;
			
			if (f == null) {
				files = worker.find(worker.hendDir, mask);
			} else {
				files = worker.find(worker.hendDir + File.separator + f, mask);
			}
		
			File[] aFiles = files.toArray(new File[0]);
		
			for (Iterator i = files.iterator(); i.hasNext(); ) {
				File hend = (File) i.next();
				System.out.println( ((hend.isFile()) ? "FILE:\t" : "DIR:\t") + hend.getName() );
			}

		} catch (NullPointerException e) {
			System.err.println(f + ": file not found.");
		}
	}
	
	/* Sort contents of a directory*/
	private void sort(String d, String type) {
		Collection <File> files = null;
	
		if (type.equals("+name"))
			files = worker.sortByName(d,1);
		if (type.equals("-name"))
			files = worker.sortByName(d,0);
		if (type.equals("+size"))
			files = worker.sortBySize(d,1);
		if (type.equals("-size"))
			files = worker.sortBySize(d,0);
		if (type.equals("+lmod"))
			files = worker.sortByLastModify(d,1);
		if (type.equals("-lmod"))
			files = worker.sortByLastModify(d,0);
			
		File[] aFiles = files.toArray(new File[0]);
		
		for (Iterator i = files.iterator(); i.hasNext(); ) {
			File hend = (File) i.next();
			System.out.println( ((hend.isFile()) ? "FILE:\t" : "DIR:\t") + hend.getName() );
		}
	}
	
	/* Help to this program */
	public void help() {
		System.out.println("\nJShell -- cross-platform shell\n\n" +
		"Commands:\n\n" + 
		"cd [PATH]" +		"\t\t Change directory to PATH, or home if there is no PATH\n" +
		"copy <NAME> <DEST>"+	"\t Copy file or directory (recursive) by NAME to DEST\n"+
		"delete <PATH>"+	"\t\t Delete file or directory (recursive) with PATH\n"+
		"dir [PATH] [MASK]"+ 	"\t Print list files in directory to PATH (matching MASK, if it is specified), or current directory if there is no PATH and MASK\n"+
		"help"+			"\t\t\t This help\n"+
		"mkfile <NAME>"+	"\t\t Create a new file with NAME\n"+
		"mkdir <NAME>"+		"\t\t Create a new directory with NAME (including any necessary but nonexistent parent directories)\n"+
		"rename <OLD> <NEW>"+	"\t Rename file or directory to NEW name\n"+
		"sort <PATH> <TYPE>"+	"\t Sort directory contents by TYPE:\n"+
		"\t+name"+		"\t\t\t Sort by name (ascending)\n"+
		"\t-name"+		"\t\t\t Sort by name (descending)\n"+
		"\t+size"+		"\t\t\t Sort by size (ascending)\n"+
		"\t-size"+		"\t\t\t Sort by size (descending)\n"+
		"\t+lmod"+		"\t\t\t Sort by last modify (ascending)\n"+
		"\t-lmod"+		"\t\t\t Sort by last modify (descending)\n"
		);
	}
	
	public static boolean isWindows() {
	/** Метод определяет, является ли эта ОС Windows (нужно для задания символа экранирования)
	*/
		String os = System.getProperty("os.name").toLowerCase();
		return (os.indexOf("win") >= 0);
 
	}
 
}

class CommandException extends ArrayIndexOutOfBoundsException {
	/** Класс предназначен для выбрасывания исключений в случае ошибочного ввода команд */
	CommandException(String eMessage) {
		super(eMessage); // Конструктор суперкласса
	}
}
